#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include "Hass-Mqtt.h"
#include <ArduinoJson.h>
#include <LittleFS.h>
#include "conf.h"

#ifdef PUSHBTN
#include <OneButton.h>
#endif
#include <Button.h>

unsigned long pre_tick = millis();
WiFiClient espClient;
HassMqtt client(espClient);

#if (KEYS >= 1)
Light light1(LIGHT_1);
HaSwitch ha1(&light1, "1", &client);
#endif
#if (KEYS >= 2)
Light light2(LIGHT_2);
HaSwitch ha2(&light2, "2", &client);
#endif
#if (KEYS >= 3)
Light light3(LIGHT_3);
HaSwitch ha3(&light3, "3", &client);
#endif
#if (KEYS >= 4)
Light light4(LIGHT_4);
HaSwitch ha4(&light4, "4", &client);
#endif

/* read mqtt config json file */
void read_config()
{
    if (LittleFS.exists("/config.json"))
    {
        // file exists, reading and loading
        Serial.println("reading config file");
        File configFile = LittleFS.open("/config.json", "r");
        if (configFile)
        {
            Serial.println("opened config file");
            size_t size = configFile.size();
            // Allocate a buffer to store contents of the file.
            std::unique_ptr<char[]> buf(new char[size]);
            configFile.readBytes(buf.get(), size);
            JsonDocument _config;
            deserializeJson(_config, buf.get());

            sprintf(client.mqtt_server, _config["mqtt_server"]);
            sprintf(client.Hass_Mqtt_user, _config["mqtt_userid"]);
            sprintf(client.Hass_Mqtt_pass, _config["mqtt_pswd"]);
            client.Hass_Mqtt_port = atoi(_config["mqtt_port"]);

            configFile.close();
        }
    }
}

// mqtt server callback
void callback(char *topic, byte *payload, unsigned int length)
{
#if (KEYS >= 1)
    if (!strcmp(topic, ha1.cmd_topic))
    {
        if (payload[1] == 'N')
        {
            ha1.light->on();
            ha1.send_state();
            Serial.println("Light_1 ON from hass");
        }
        else
        {
            ha1.light->off();
            ha1.send_state();
            Serial.println("Light_1 OFF from hass");
        }
    }
#endif
#if (KEYS >= 2)
    if (!strcmp(topic, ha2.cmd_topic))
    {
        if (payload[1] == 'N')
        {
            ha2.light->on();
            ha2.send_state();
            Serial.println("Light_2 ON from hass");
        }
        else
        {
            ha2.light->off();
            ha2.send_state();
            Serial.println("Light_2 OFF from hass");
        }
    }
#endif
#if (KEYS >= 3)
    if (!strcmp(topic, ha3.cmd_topic))
    {
        // Serial.println("call in");
        if (payload[1] == 'N')
        {
            ha3.light->on();
            ha3.send_state();
            Serial.println("Light_3 ON from hass");
        }
        else
        {
            ha3.light->off();
            ha3.send_state();
            Serial.println("Light_3 OFF from hass");
        }
    }
#endif
#if (KEYS >= 4)
    if (!strcmp(topic, ha4.cmd_topic))
    {
        // Serial.println("call in");
        if (payload[1] == 'N')
        {
            ha4.light->on();
            ha4.send_state();
            Serial.println("Light_4 ON from hass");
        }
        else
        {
            ha4.light->off();
            ha4.send_state();
            Serial.println("Light_4 OFF from hass");
        }
    }
#endif
}

HaSwitch::HaSwitch(Light *light, const char *index, PubSubClient *Client)
{
    JsonDocument root;

    sprintf(MQTT_CLIENT_ID, "%06X", ESP.getChipId());
    sprintf(NAME, Hass_Mqtt_name, MQTT_CLIENT_ID, index);
    sprintf(MQTT_CONFIG_TOPIC, MQTT_CONFIG_TOPIC_TEMPLATE, NAME);
    sprintf(MQTT_STATE_TOPIC, MQTT_STATE_TOPIC_TEMPLATE, NAME);
    sprintf(MQTT_COMMAND_TOPIC, MQTT_COMMAND_TOPIC_TEMPLATE, NAME);

    char MQTT___TOPIC[sizeof(NAME) + sizeof(MQTT_AUTO_DISCOVER) - 2] = {0};
    sprintf(MQTT___TOPIC, MQTT_AUTO_DISCOVER, NAME);

    root["~"] = MQTT___TOPIC;
    root["name"] = NAME;
    root["uniq_id"] = NAME;
    root["stat_t"] = "~/state";
    root["cmd_t"] = "~/set";
    root["schema"] = "json";
    
    serializeJson(root, jsonBuffer);

    this->light = light;
    this->Id = NAME;
    this->mqttclient = Client;
    this->config_topic = MQTT_CONFIG_TOPIC;
    this->cmd_topic = MQTT_COMMAND_TOPIC;
    this->state_topic = MQTT_STATE_TOPIC;
    this->config_text = jsonBuffer;
}

void HaSwitch::send_state()
{
    if (light->state)
        mqttclient->publish(state_topic, "ON");
    else
        mqttclient->publish(state_topic, "OFF");
}

//report the light state while mqtt is alive
void HaSwitch::handle()
{
    if (light->changed)
    {
        if (light->state)
        {
            /* code to turn off lights */
            light->off();
            Serial.printf(Id);
            Serial.println("  OFF from Box");
        }
        else
        {
            /* code to turn on lights */
            light->on();
            Serial.printf(Id);
            Serial.println("  ON from Box");
        }
        light->changed = 0;
        if (WiFi.status() == WL_CONNECTED)
        {
            if (mqttclient->connected())
            {
                send_state();
            }
        }
    }
}

void HassMqtt::handle()
{
    if ((millis() - pre_tick) > 5000)
    {
        if (WiFi.status() == WL_CONNECTED)
        {
            if (!this->connected())
            {
                this->reconnect();
            }
        }
        pre_tick = millis();
            }
            this->loop();
}

void HassMqtt::keyhandle()
{
#if (KEYS >= 1)
    ha1.handle();
#endif
#if (KEYS >= 2)
    ha2.handle();
#endif
#if (KEYS >= 3)
    ha3.handle();
#endif
#if (KEYS >= 4)
    ha4.handle();
#endif
}

void HaSwitch::begin()
{
    if(!this->mqttclient->publish(config_topic, config_text, 1))
    {
        Serial.printf(Id);
        Serial.println("  Init Faild!");
    }
}

void HassMqtt::reconnect()
{
    if (!this->connected())
    {
        read_config();
        Serial.println("Attempting MQTT connection...");
        this->setServer(mqtt_server, client.Hass_Mqtt_port);
        if (this->connect(MQTT_CLIENT_ID, Hass_Mqtt_user, Hass_Mqtt_pass))
        {
            Serial.println("connected");
#if (KEYS >= 1)
            ha1.begin();
            this->subscribe(ha1.cmd_topic);
            ha1.send_state();
#endif
#if (KEYS >= 2)
            ha2.begin();
            client.subscribe(ha2.cmd_topic);
            ha2.send_state();
#endif
#if (KEYS >= 3)
            ha3.begin();
            client.subscribe(ha3.cmd_topic);
            ha3.send_state();
#endif
#if (KEYS >= 4)
            ha4.begin();
            client.subscribe(ha4.cmd_topic);
            ha4.send_state();
#endif
        }
        else
        {
            Serial.print("faild, rc=");
            Serial.println(client.state());
            delay(5);
        }
    }
}

#if (KEYS >= 1)
void IRAM_ATTR PAD_1_callback(){
    ha1.light->changed = 1;
}
#endif
#if (KEYS >= 2)
void IRAM_ATTR PAD_2_callback(){
    ha2.light->changed = 1;
}
#endif
#if (KEYS >= 3)
void IRAM_ATTR PAD_3_callback(){
    ha3.light->changed = 1;
}
#endif
#if (KEYS >= 4)
void IRAM_ATTR PAD_4_callback(){
    ha4.light->changed = 1;
}
#endif

void HassMqtt::keybegin()
{
    pinMode(LIGHT_1, OUTPUT);
    pinMode(LIGHT_2, OUTPUT);
    pinMode(LIGHT_3, OUTPUT);
    pinMode(LIGHT_4, OUTPUT);
    button_init();
}

void HassMqtt::begin()
{
    sprintf(MQTT_CLIENT_ID, "%06X", ESP.getChipId());
    client.setServer(mqtt_server, client.Hass_Mqtt_port);
    client.setKeepAlive(60);
    client.setCallback(::callback);
    client.setBufferSize(512);
    handle();
}

Light::Light(uint8_t pin)
{
    this->pin = pin;
    this->state = LIGHT_OFF;
    this->changed = false;
    pinMode(this->pin, OUTPUT);
    digitalWrite(this->pin, LIGHT_OFF);
    this->state = LIGHT_OFF;
}

void Light::on()
{
    digitalWrite(this->pin, LIGHT_ON);
    this->state = LIGHT_ON;
}

void Light::off()
{
    digitalWrite(this->pin, LIGHT_OFF);
    this->state = LIGHT_OFF;
}

void Light::toggle()
{
    if (!this->state)
    {
        digitalWrite(this->pin, LIGHT_ON);
        this->state = LIGHT_ON;
    }
    else
    {
        digitalWrite(this->pin, LIGHT_OFF);
        this->state = LIGHT_OFF;
    }
}